package common;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

public class Header {
  /* Parts of the Header. */
  private int 	sequenceNumber;
  private int 	acknowledgementNumber;
  private short advertisedWindow;
  private byte  type;
  private int	length = 0;			
  private long  checkSum;
  /* Defaults. */
  public static final int   NULL_SEQUENCE_NUMBER        = -1;
  public static final int   NULL_ACKNOWLEDGEMENT_NUMBER = -2;
  public static final short NULL_ADVERTISED_WINDOW      = -3;
  public static final long  NULL_CHECKSUM				= 0;
  public static final int   HEADER_SIZE                 = calcHeaderSize();
  
  /**
   * Sender uses this for construction
   * 
   * @param seq
   * @param type
   */
  public Header(int seq, byte type, int length) {
    this(seq, NULL_ACKNOWLEDGEMENT_NUMBER, NULL_ADVERTISED_WINDOW, type, length, NULL_CHECKSUM);
  }
  
  /**
   * Receiver uses this for construction NOTE: Ack number should be
   * the next sequence number we expect
   * 
   * @param ack
   * @param adWin
   * @param type
   */
  public Header(int ack, short adWin, byte type) {
    this(NULL_SEQUENCE_NUMBER, ack, adWin, type, 0, NULL_CHECKSUM);
  }
  
  /**
   * Main Constructor.
   * 
   * @param seq
   * @param ack
   * @param win
   * @param type
   */
  private Header(int seq, int ack, short win, byte type, int length, long checkSum) {
    this.sequenceNumber = seq;
    this.acknowledgementNumber = ack;
    this.advertisedWindow = win;
    this.type = type;
    this.length = length;
    this.checkSum = checkSum;
  }
  
  /**
   * @return
   */
  public int getSeqNum() {
    return sequenceNumber;
  }
  
  /**
   * @return
   */
  public int getAckNum() {
    return acknowledgementNumber;
  }
  
  /**
   * @return
   */
  public short getAdWindow() {
    return advertisedWindow;
  }
  
  /**
   * @return
   */
  public byte getPacketType() {
    return type;
  }
  
  public int getLength() {
	  return length;
  }

  public long getCheckSum() {
	  return checkSum;
  }
  
  public void setCheckSum(long cs) {
	  this.checkSum = cs;
  }
  
  /**
   * Serializes the Header into a new Byte Array.
   * 
   * @return a Byte Array.
   * @throws IOException
   */
  public byte[] serialize() throws IOException {
    // header size should be constant
    int size = HEADER_SIZE;
    byte[] serializedHeader = new byte[size];
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    // header size should be constant
    dos.writeInt(sequenceNumber); // write the seq num to output
                                  // stream in dos (write to baos)
    dos.writeInt(acknowledgementNumber);
    dos.writeShort(advertisedWindow);
    dos.write(type);
    dos.writeInt(length);
    dos.writeLong(checkSum);
    
    dos.close();
    serializedHeader = baos.toByteArray();
    return serializedHeader;
  }
  
  /**
   * Deserializes the Header.
   * 
   * @param serializedHeader
   * @return The new Header.
   * @throws IOException
   */
  public static Header deserialize(byte[] serializedHeader) throws IOException {
    // TODO: error checking
    int seq = NULL_SEQUENCE_NUMBER;
    int ack = NULL_ACKNOWLEDGEMENT_NUMBER;
    short adWin = NULL_ADVERTISED_WINDOW;
    byte type = 0;
    int length = 0;
    long checkSum = 0;
    
    ByteArrayInputStream bais = new ByteArrayInputStream(serializedHeader);
    DataInputStream dis = new DataInputStream(bais);
    
    seq = dis.readInt();
    ack = dis.readInt();
    adWin = dis.readShort();
    type = dis.readByte();
    length = dis.readInt();
    checkSum = dis.readLong();
    
    dis.close();
    Header theHeader = new Header(seq, ack, adWin, type, length, checkSum);
    return theHeader;
  }
  
  /**
   * Calculates the size of the header by looking at the contained
   * variables sizes.
   * 
   * @return the size of the Header.
   */
  private static int calcHeaderSize() {
	// seq num and ack num
    int size = JavaPrimitiveSize.sizeOf(JavaPrimitiveType.INTEGER) * 2;
    // advertised window
    size += JavaPrimitiveSize.sizeOf(JavaPrimitiveType.SHORT);
    // packet type
    size += JavaPrimitiveSize.sizeOf(JavaPrimitiveType.BYTE);
    // length
    size += JavaPrimitiveSize.sizeOf(JavaPrimitiveType.INTEGER);
    // check sum
    size += JavaPrimitiveSize.sizeOf(JavaPrimitiveType.LONG);
    return size;
  }
  
  public String toString() {
    return "Header: \nAck Number: " + this.acknowledgementNumber + "\n"
           + "Window Size: " + this.advertisedWindow + "\n" + "Seq Number: "
           + this.sequenceNumber + "\n" + "Type: " + this.type + "\n" 
           + "Length: " + this.length + "\n" + "Checksum: " + this.checkSum + "\n";
  }
}
